Skip to content

feat: add semantic adapter APIs for visual-layer consumers - #91

Merged
leoafarias merged 3 commits into
mainfrom
feat/remix-adapter-semantics
Aug 19, 2026
Merged

feat: add semantic adapter APIs for visual-layer consumers#91
leoafarias merged 3 commits into
mainfrom
feat/remix-adapter-semantics

Conversation

@leoafarias

Copy link
Copy Markdown
Member

What

Three additive APIs that Remix (a visual-only layer over naked_ui) needs so Naked can be the sole semantics and behavior owner:

  • NakedButton.semanticHint — announced on the same Semantics node as the button role, label, enabled state, and tap/long-press actions. Without it, a consumer that wants a hint must wrap the button in a second Semantics node, which is exactly the duplicate-node pattern a headless library should prevent.
  • NakedSelect.semanticValue — a human-readable value for the current selection. Today the trigger announces value?.toString(), which for any non-primitive T reads as Instance of 'Foo'. The trigger keeps the merged button + expanded + value contract because SemanticsRole.comboBox still fails debug semantics checks on stable (Missing checks for role, Missing Combo Box Role Implementation flutter/flutter#172918 — note the TODO in semantics.dart cites #159741, which is closed).
  • NakedRadioGroup<T> — a thin wrapper over Flutter's RadioGroup.

Why bring back a radio group?

A NakedRadioGroup existed pre-1.0 and was deliberately dropped when the package hardened for 1.0, delegating grouping to Flutter's RadioGroup. That delegation stands — NakedRadio still requires Flutter's registry. This wrapper only supplies what RadioGroup measurably lacks:

  1. A nullable onChanged. Flutter's RadioGroup.onChanged is required, so "disabled group" can't be expressed; consumers each invent a no-op adapter.
  2. Group-level enabled. Radios inherit it via a typed NakedRadioGroupScope<T>, matching Flutter's typed RadioGroup.maybeOf<T> registry lookup so nested groups of different value types can't read each other's enabled state.
  3. An accessible group label. RadioGroup publishes the SemanticsRole.radioGroup node but accepts no label. The label here is a plain labeled container around Flutter's role node — deliberately not a second radioGroup role node, which would announce the group twice (verified by test: exactly one role node with or without a label).

Tests

  • semanticHint lives on the single button node (label + hint + tap action on one node).
  • semanticValue announced instead of T.toString() for an Object value.
  • NakedRadioGroup: tap selects through group onChanged; onChanged: null disables every radio (semantics isEnabled false); enabled: false blocks selection; label produces exactly one labeled container and exactly one radioGroup role node; no label still leaves Flutter's single role node; nested groups of different types keep enabled state aligned.

flutter analyze clean; 711 tests passed, 3 skipped (Flutter 3.44.0 stable).

Consumer

Remix 1.0.0-beta.4 (visual-only-over-Naked pass) consumes all three via a temporary path override; compiling Remix against published 1.0.0-beta.11 fails on exactly these three APIs and nothing else.

- NakedButton.semanticHint on the same Semantics node as the button
  role, label, enabled state, and tap/long-press actions.
- NakedSelect.semanticValue so the trigger announces a human-readable
  selection instead of T.toString(). The trigger keeps the merged
  button + expanded + value contract because SemanticsRole.comboBox
  still fails debug checks (flutter/flutter#172918).
- NakedRadioGroup: a thin wrapper over Flutter's RadioGroup supplying
  what it lacks - a nullable onChanged (null means disabled), a group
  enabled state radios inherit via a typed NakedRadioGroupScope, and
  an optional accessible group label. Flutter's RadioGroup keeps the
  single SemanticsRole.radioGroup node; the label never adds a second
  role node.
@github-actions

Copy link
Copy Markdown
Contributor

LCOV of commit 30aef9a during Flutter CI #293

	(use "lcov --ignore-errors deprecated,deprecated ..." to suppress this warning)
Reading tracefile /tmp/__zgosalvez_github-actions-report-lcov/lcov.info.
Summary coverage rate:
  source files: 23
  lines.......: 90.4% (3110 of 3441 lines)
  functions...: no data found
  branches....: no data found
Message summary:
  1 warning message:
    deprecated: 1

Files changed coverage rate:
  	(use "lcov --ignore-errors deprecated,deprecated ..." to suppress this warning)
                                                                   |Lines       |Functions  |Branches    
  Filename                                                         |Rate     Num|Rate    Num|Rate     Num
  packages/naked_ui/lib/src/naked_button.dart                      |93.0%    100|    -     0|    -      0
  packages/naked_ui/lib/src/naked_radio.dart                       |86.7%     83|    -     0|    -      0
  packages/naked_ui/lib/src/naked_select.dart                      |87.6%    170|    -     0|    -      0

- Hide NakedRadioGroupScope from the public barrel (plumbing, mirrors
  Flutter's private _RadioGroupStateScope; precedent: hide
  NakedStateScopeBuilder).
- Strengthen the group-label test: assert the labeled container has no
  role and contains Flutter's radioGroup role node, instead of two
  independent counts that can't distinguish structure.
- Add a mismatched-type nesting test that fails if the enabled scope
  lookup were untyped (radio must bind to its own-typed, disabled group
  past a nearer enabled group of another type).
- Document the same-type plain-RadioGroup nesting boundary on
  NakedRadioGroup.
@github-actions

Copy link
Copy Markdown
Contributor

LCOV of commit 044865b during Flutter CI #294

	(use "lcov --ignore-errors deprecated,deprecated ..." to suppress this warning)
Reading tracefile /tmp/__zgosalvez_github-actions-report-lcov/lcov.info.
Summary coverage rate:
  source files: 23
  lines.......: 90.4% (3110 of 3441 lines)
  functions...: no data found
  branches....: no data found
Message summary:
  1 warning message:
    deprecated: 1

Files changed coverage rate:
  	(use "lcov --ignore-errors deprecated,deprecated ..." to suppress this warning)
                                                                   |Lines       |Functions  |Branches    
  Filename                                                         |Rate     Num|Rate    Num|Rate     Num
  packages/naked_ui/lib/src/naked_button.dart                      |93.0%    100|    -     0|    -      0
  packages/naked_ui/lib/src/naked_radio.dart                       |86.7%     83|    -     0|    -      0
  packages/naked_ui/lib/src/naked_select.dart                      |87.6%    170|    -     0|    -      0

@leoafarias
leoafarias marked this pull request as ready for review August 19, 2026 00:08
@leoafarias
leoafarias merged commit 9560b8e into main Aug 19, 2026
6 checks passed
@leoafarias
leoafarias deleted the feat/remix-adapter-semantics branch August 19, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant